#pragma once
// -------------------------------------------------
// ZED Kit
// -------------------------------------------------
//   File Name: string.hpp
//      Author: Ziming Li
//     Created: 2021-02-08
// -------------------------------------------------
// Copyright (C) 2021 MingYang Software Technology.
// -------------------------------------------------

#ifndef ZED_STRING_HPP
#define ZED_STRING_HPP

#include "./build_macros.hpp"

#include <string>
#include <string_view>
#include "./assert.h"
#include "./ctype.hpp"
#include "./type_traits.hpp"
#ifdef _Z_OS_POSIX
#   include <strings.h>
#endif
#ifdef __OBJC__
#   import <Foundation/Foundation.h>
#endif

namespace zed {

template <typename CharT>
using string_piece = std::basic_string_view<CharT>;

/**
 * Comparisons
 */

int stricmp(const char *psz1, const char *psz2);
int strnicmp(const char *ps1, const char *ps2, size_t n);

template <typename S1, typename S2, typename = std::enable_if<chartypes_same<S1, S2>::value>>
int strcmp(const S1 &s1, const S2 &s2);

template <typename S1, typename S2, typename = std::enable_if<chartypes_same<S1, S2>::value>>
int stricmp(const S1 &s1, const S2 &s2);

template <typename S1, typename S2, typename = std::enable_if<chartypes_same<S1, S2>::value>>
bool strequ(const S1 &s1, const S2 &s2) { return 0 == zed::strcmp(s1, s2); }

template <typename S1, typename S2, typename = std::enable_if<chartypes_same<S1, S2>::value>>
bool striequ(const S1 &s1, const S2 &s2) { return 0 == zed::stricmp(s1, s2); }

#ifdef __OBJC__

/**
 * NSString
 */

NSString* to_ns_string(std::string_view src, NSStringEncoding encoding = NSUTF8StringEncoding);

#endif // __OBJC__

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementations

namespace detail {

template <typename T>
class string_adaptor
{
public:
    using char_type = typename T::value_type;
    constexpr static auto npos = T::npos;

    explicit string_adaptor(const T &s) : m_s(s) {}

    size_t find(const char_type *psz, size_t pos = 0) const { return m_s.find(psz, pos); }
    size_t find_first_not_of(const char_type *psz) const { return m_s.find_first_not_of(psz); }
    size_t find_last_not_of(const char_type *psz) const { return m_s.find_last_not_of(psz); }

    std::basic_string_view<char_type> sub_piece(size_t pos, size_t count = npos) const
    {
        count = std::min(m_s.length() - pos, count);
        return count > 0 ? std::basic_string_view<char_type>(m_s.data() + pos, count) : std::basic_string_view<char_type>();
    }
private:
    const T &m_s;
};

template <typename CharT>
class string_adaptor<const CharT *>
{
public:
    using char_type = CharT;
    constexpr static auto npos = std::basic_string<CharT>::npos;

    explicit string_adaptor(const CharT *psz) : m_psz(psz) {}

    size_t find_first_not_of(const char_type *psz) const
    {
        for (const CharT *p = m_psz; '\0' != *p; ++p)
        {
            if (!in_chars(psz, *p))
                return p - m_psz;
        }
        return npos;
    }
    size_t find_last_not_of(const char_type *psz) const
    {
        size_t i = 0, ret = 0;
        for (const CharT *p = m_psz; '\0' != *p; ++p)
        {
            if (!in_chars(psz, *p))
                ret = i;
            ++i;
        }
        return ret;
    }

    std::basic_string_view<char_type> sub_piece(size_t pos, size_t count = npos) const
    {
        if (npos == count)
        {
            count = 0;
            for (const CharT *p = m_psz + pos; '\0' != *p; ++p)
                ++count;
        }
        return std::basic_string_view<char_type>(m_psz + pos, count);
    }
private:
    static bool in_chars(const CharT *chars, int ch)
    {
        while ('\0' != *chars)
        {
            if (*chars == ch)
                return true;
            ++chars;
        }
        return false;
    }

    const CharT *m_psz;
};

template <class String>
class char_iterator
{
public:
    using char_type = typename String::value_type;

    explicit char_iterator(const String &s) : m_ps(s.data()), m_left(s.length()) {}

    char_type operator*() const { return *m_ps; }
    char_iterator& operator++()
    {
        ZASSERT(!reach_the_end());
        ++m_ps; --m_left;
        return *this;
    }

    bool reach_the_end(void) const { return 0 == m_left; }

    const char_type* data(void) const { ZASSERT(!reach_the_end()); return m_ps; }

    std::basic_string_view<char_type> take_left_piece(void)
    {
        if (m_left > 0)
        {
            auto start = m_ps;
            size_t len = m_left;

            m_ps  += m_left;
            m_left = 0;
            return std::basic_string_view<char_type>(start, len);
        }
        else
        {
            return std::basic_string_view<char_type>();
        }
    }
private:
    const char_type *m_ps;
    size_t m_left;
};

template <typename CharT>
class char_iterator<const CharT *>
{
public:
    using char_type = CharT;

    explicit char_iterator(const CharT *psz) : m_psz(psz) {}

    char_type operator*() const { return *m_psz; }
    char_iterator& operator++()
    {
        ZASSERT(!reach_the_end());
        ++m_psz;
        return *this;
    }

    bool reach_the_end(void) const { return *m_psz == '\0'; }

    const char_type* data(void) const { return m_psz; }

    std::basic_string_view<char_type> take_left_piece(void)
    {
        auto start = m_psz;
        size_t len = 0;
        while ('\0' != *m_psz)
        {
            ++m_psz;
            ++len;
        }
        return len > 0 ? std::basic_string_view<char_type>(start, len) : std::basic_string_view<char_type>();
    }
private:
    const char_type *m_psz;
};

template <typename S>
char_iterator<S> wrap_char_iterator(const S &s)
{
    return char_iterator<S>(s);
}

template <typename CharT>
char_iterator<const CharT *> wrap_char_iterator(const CharT *psz)
{
    return char_iterator<const CharT *>(psz);
}

} // namespace detail

template <typename S1, typename S2, typename>
int strcmp(const S1 &s1, const S2 &s2)
{
    auto it1 = detail::wrap_char_iterator(s1);
    auto it2 = detail::wrap_char_iterator(s2);
    while (!it1.reach_the_end() && !it2.reach_the_end())
    {
        auto c1 = *it1;
        auto c2 = *it2;
        if (c1 != c2)
        {
            if (c1 > c2)
                return 1;
            if (c1 < c2)
                return -1;
        }
        ++it1; ++it2;
    }

    if (!it1.reach_the_end())
        return 1;
    if (!it2.reach_the_end())
        return -1;
    return 0;
}

template <typename S1, typename S2, typename>
int stricmp(const S1 &s1, const S2 &s2)
{
    auto it1 = detail::wrap_char_iterator(s1);
    auto it2 = detail::wrap_char_iterator(s2);
    while (!it1.reach_the_end() && !it2.reach_the_end())
    {
        auto c1 = *it1;
        auto c2 = *it2;
        if (c1 != c2)
        {
            if (zed::isalpha(c1) && zed::isalpha(c2))
            {
                if (isupper(c1))
                    c1 = std::tolower(c1);
                if (isupper(c2))
                    c2 = std::tolower(c2);
            }

            if (c1 > c2)
                return 1;
            if (c1 < c2)
                return -1;
        }
        ++it1; ++it2;
    }

    if (!it1.reach_the_end())
        return 1;
    if (!it2.reach_the_end())
        return -1;
    return 0;
}

#if defined(_Z_OS_WINDOWS)
inline int stricmp(const char *psz1, const char *psz2)
{
    return ::_stricmp(psz1, psz2);
}

inline int strnicmp(const char *ps1, const char *ps2, size_t n)
{
    return ::_strnicmp(ps1, ps2, n);
}
#elif defined(_Z_OS_POSIX)
inline int stricmp(const char *psz1, const char *psz2)
{
    return ::strcasecmp(psz1, psz2);
}

inline int strnicmp(const char *ps1, const char *ps2, size_t n)
{
    return ::strncasecmp(ps1, ps2, n);
}
#endif

#ifdef __OBJC__
inline NSString* to_ns_string(std::string_view src, NSStringEncoding encoding)
{
    return [[NSString alloc] initWithBytes: src.data()
                                    length: src.length()
                                  encoding: encoding];
}
#endif // __OBJC__

} // namespace zed

#endif // ZED_STRING_HPP
